//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <gmock/gmock.h>

#include <sqbind/sqbBind.h>

#include "fixtures/SquirrelFixture.h"
#include "mocks/MockFunctionWithNoParameters.h"
//----------------------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------------------
typedef SquirrelFixture BindFunctionTest;

//----------------------------------------------------------------------------------------------------------------------
typedef SquirrelFixture BindClassFunctionTest;

//----------------------------------------------------------------------------------------------------------------------
typedef SquirrelFixture BindSingletonFunctionTest;

//----------------------------------------------------------------------------------------------------------------------
// TestInvalidCall
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindFunctionTest, TestInvalidCall)
{
  EXPECT_FALSE(sqb::Bind::BindFunction(m_vm, 1, &MockFunctionWithNoParameters::VoidFunction, _SC("test")));

  sq_pushroottable(m_vm);
  EXPECT_FALSE(sqb::Bind::BindFunction(m_vm, 1, &MockFunctionWithNoParameters::VoidFunction, nullptr));
  EXPECT_FALSE(sqb::Bind::BindFunction(m_vm, 1, &MockFunctionWithNoParameters::VoidFunction, _SC("")));
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
// TestInvalidCall
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindClassFunctionTest, TestInvalidCall)
{
  EXPECT_FALSE(sqb::Bind::BindClassFunction<MockFunctionWithNoParameters>(m_vm, 1, &MockFunctionWithNoParameters::ClassVoidFunction, _SC("test")));

  sq_newclass(m_vm, SQFalse);
  EXPECT_FALSE(sqb::Bind::BindClassFunction<MockFunctionWithNoParameters>(m_vm, 1, &MockFunctionWithNoParameters::ClassVoidFunction, nullptr));
  EXPECT_FALSE(sqb::Bind::BindClassFunction<MockFunctionWithNoParameters>(m_vm, 1, &MockFunctionWithNoParameters::ClassVoidFunction, _SC("")));
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
// TestInvalidCall
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindSingletonFunctionTest, TestInvalidCall)
{
  MockFunctionWithNoParameters mock;
  EXPECT_FALSE(sqb::Bind::BindSingletonFunction(m_vm, 1, &mock, &MockFunctionWithNoParameters::ClassVoidFunction, _SC("test")));

  sq_newclass(m_vm, SQFalse);
  EXPECT_FALSE(sqb::Bind::BindSingletonFunction(m_vm, 1, &mock, &MockFunctionWithNoParameters::ClassVoidFunction, nullptr));
  EXPECT_FALSE(sqb::Bind::BindSingletonFunction(m_vm, 1, &mock, &MockFunctionWithNoParameters::ClassVoidFunction, _SC("")));
  EXPECT_FALSE(sqb::Bind::BindSingletonFunction<MockFunctionWithNoParameters>(m_vm, 1, nullptr, &MockFunctionWithNoParameters::ClassVoidFunction, _SC("MockFunctionWithNoParameters")));
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
// Test0ParametersReturnVoid
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindFunctionTest, Test0ParametersReturnVoid)
{
  ::testing::StrictMock<MockFunctionWithNoParameters> mock;
  MockFunctionWithNoParameters::m_instance = &mock;

  EXPECT_CALL(mock, ClassVoidFunction())
    .Times(2);

  // bind the objects required for calling the function
  //
  sq_pushroottable(m_vm);

  EXPECT_TRUE(sqb::Bind::BindFunction(m_vm, -1, &MockFunctionWithNoParameters::VoidFunction, _SC("VoidFunction")));

  sq_pushstring(m_vm, _SC("Mock"), -1);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_TRUE(sqb::Bind::BindFunction(m_vm, 3, &MockFunctionWithNoParameters::VoidFunction, _SC("VoidFunction")));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -3));

  sq_poptop(m_vm);

  CompileAndSucceedCall("VoidFunction()");
  CompileAndSucceedCall("Mock.VoidFunction()");
}

//----------------------------------------------------------------------------------------------------------------------
// Test0ParametersReturnVoid
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindClassFunctionTest, Test0ParametersReturnVoid)
{
  ::testing::StrictMock<MockFunctionWithNoParameters> mock;
  MockFunctionWithNoParameters::m_instance = &mock;

  EXPECT_CALL(mock, ClassVoidFunction())
    .Times(1);
  EXPECT_CALL(mock, ConstClassVoidFunction())
    .Times(1);

  // bind the objects required for calling the function
  //
  sq_pushroottable(m_vm);

  // bind the mock class
  //
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_settypetag(m_vm, -1, sqb::ClassTypeTag<MockFunctionWithNoParameters>::Get()));
  HSQOBJECT class_object;
  sq_getstackobj(m_vm, -1, &class_object);
  sqb::ClassTypeTag<MockFunctionWithNoParameters>::Get()->SetClassObject(m_vm, class_object);
  EXPECT_TRUE(sqb::Bind::BindClassFunction<MockFunctionWithNoParameters>(m_vm, -1, &MockFunctionWithNoParameters::ClassVoidFunction, _SC("ClassVoidFunction")));
  EXPECT_TRUE(sqb::Bind::BindClassFunction<MockFunctionWithNoParameters>(m_vm, 2, &MockFunctionWithNoParameters::ConstClassVoidFunction, _SC("ConstClassVoidFunction")));
  sq_poptop(m_vm);

  EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, static_cast<MockFunctionWithNoParameters *>(&mock), _SC("mock")));

  sq_poptop(m_vm);

  CompileAndSucceedCall("mock.ClassVoidFunction()");
  CompileAndSucceedCall("mock.ConstClassVoidFunction()");
}

//----------------------------------------------------------------------------------------------------------------------
// Test0ParametersReturnVoid
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindSingletonFunctionTest, Test0ParametersReturnVoid)
{
  ::testing::StrictMock<MockFunctionWithNoParameters> mock;
  MockFunctionWithNoParameters::m_instance = &mock;

  EXPECT_CALL(mock, ClassVoidFunction())
    .Times(1);
  EXPECT_CALL(mock, ConstClassVoidFunction())
    .Times(1);

  // bind the objects required for calling the function
  //
  sq_pushroottable(m_vm);

  // bind the mock class
  //
  sq_pushstring(m_vm, _SC("Mock"), -1);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_TRUE(sqb::Bind::BindSingletonFunction<MockFunctionWithNoParameters>(m_vm, -1, &mock, &MockFunctionWithNoParameters::ClassVoidFunction, _SC("ClassVoidFunction")));
  EXPECT_TRUE(sqb::Bind::BindSingletonFunction<MockFunctionWithNoParameters>(m_vm, 1, &mock, &MockFunctionWithNoParameters::ConstClassVoidFunction, _SC("ConstClassVoidFunction")));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -3));

  sq_poptop(m_vm);

  CompileAndSucceedCall("Mock.ClassVoidFunction()");
  CompileAndSucceedCall("ConstClassVoidFunction()");
}

//----------------------------------------------------------------------------------------------------------------------
// Test0Parameters
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindFunctionTest, Test0Parameters)
{
  ::testing::StrictMock<MockFunctionWithNoParameters> mock;
  MockFunctionWithNoParameters::m_instance = &mock;

  sq_pushroottable(m_vm);
  sqb::Bind::BindClass<BoundClass, sqb::NoBaseClass>(m_vm, -1, _SC("BoundClass"));
  sqb::Bind::BindClass<OtherBoundClass, sqb::NoBaseClass>(m_vm, -1, _SC("OtherBoundClass"));
  sq_poptop(m_vm);

  BoundClass expected_result(0xffffffff);
  EXPECT_CALL(mock, ClassFunction())
    .Times(2)
    .WillRepeatedly(::testing::Return(expected_result));

  // bind the objects required for calling the function
  //
  sq_pushroottable(m_vm);

  EXPECT_TRUE(sqb::Bind::BindFunction(m_vm, -1, &MockFunctionWithNoParameters::Function, _SC("Function")));

  sq_pushstring(m_vm, _SC("Mock"), -1);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_TRUE(sqb::Bind::BindFunction(m_vm, 3, &MockFunctionWithNoParameters::Function, _SC("Function")));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -3));

  sq_poptop(m_vm);

  BoundClass actual_result = CompileAndCallReturnResult<BoundClass>("return Function()");
  EXPECT_EQ(expected_result, actual_result);
  actual_result = CompileAndCallReturnResult<BoundClass>("return Mock.Function()");
  EXPECT_EQ(expected_result, actual_result);
}

//----------------------------------------------------------------------------------------------------------------------
// Test0Parameters
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindClassFunctionTest, Test0Parameters)
{
  ::testing::StrictMock<MockFunctionWithNoParameters> mock;
  MockFunctionWithNoParameters::m_instance = &mock;

  sq_pushroottable(m_vm);
  sqb::Bind::BindClass<BoundClass, sqb::NoBaseClass>(m_vm, -1, _SC("BoundClass"));
  sqb::Bind::BindClass<OtherBoundClass, sqb::NoBaseClass>(m_vm, -1, _SC("OtherBoundClass"));
  sq_poptop(m_vm);

  BoundClass expected_result(0xffffffff);
  EXPECT_CALL(mock, ClassFunction())
    .WillOnce(::testing::Return(expected_result));
  EXPECT_CALL(mock, ConstClassFunction())
    .WillOnce(::testing::Return(expected_result));

  // bind the objects required for calling the function
  //
  sq_pushroottable(m_vm);

  // bind the mock class
  //
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_settypetag(m_vm, -1, sqb::ClassTypeTag<MockFunctionWithNoParameters>::Get()));
  HSQOBJECT class_object;
  sq_getstackobj(m_vm, -1, &class_object);
  sqb::ClassTypeTag<MockFunctionWithNoParameters>::Get()->SetClassObject(m_vm, class_object);
  EXPECT_TRUE(sqb::Bind::BindClassFunction<MockFunctionWithNoParameters>(m_vm, -1, &MockFunctionWithNoParameters::ClassFunction, _SC("ClassFunction")));
  EXPECT_TRUE(sqb::Bind::BindClassFunction<MockFunctionWithNoParameters>(m_vm, 2, &MockFunctionWithNoParameters::ConstClassFunction, _SC("ConstClassFunction")));
  sq_poptop(m_vm);

  EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, static_cast<MockFunctionWithNoParameters *>(&mock), _SC("mock")));

  sq_poptop(m_vm);

  BoundClass actual_result = CompileAndCallReturnResult<BoundClass>("return mock.ClassFunction()");
  EXPECT_EQ(expected_result, actual_result);
  actual_result = CompileAndCallReturnResult<BoundClass>("return mock.ConstClassFunction()");
  EXPECT_EQ(expected_result, actual_result);
}

//----------------------------------------------------------------------------------------------------------------------
// Test0Parameters
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindSingletonFunctionTest, Test0Parameters)
{
  ::testing::StrictMock<MockFunctionWithNoParameters> mock;
  MockFunctionWithNoParameters::m_instance = &mock;

  sq_pushroottable(m_vm);
  sqb::Bind::BindClass<BoundClass, sqb::NoBaseClass>(m_vm, -1, _SC("BoundClass"));
  sqb::Bind::BindClass<OtherBoundClass, sqb::NoBaseClass>(m_vm, -1, _SC("OtherBoundClass"));
  sq_poptop(m_vm);

  BoundClass expected_result(0xffffffff);
  EXPECT_CALL(mock, ClassFunction())
    .WillOnce(::testing::Return(expected_result));
  EXPECT_CALL(mock, ConstClassFunction())
    .WillOnce(::testing::Return(expected_result));

  // bind the objects required for calling the function
  //
  sq_pushroottable(m_vm);

  // bind the mock class
  //
  sq_pushstring(m_vm, _SC("Mock"), -1);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_TRUE(sqb::Bind::BindSingletonFunction<MockFunctionWithNoParameters>(m_vm, -1, &mock, &MockFunctionWithNoParameters::ClassFunction, _SC("ClassFunction")));
  EXPECT_TRUE(sqb::Bind::BindSingletonFunction<MockFunctionWithNoParameters>(m_vm, 1, &mock, &MockFunctionWithNoParameters::ConstClassFunction, _SC("ConstClassFunction")));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -3));

  sq_poptop(m_vm);

  BoundClass actual_result = CompileAndCallReturnResult<BoundClass>("return Mock.ClassFunction()");
  EXPECT_EQ(expected_result, actual_result);
  actual_result = CompileAndCallReturnResult<BoundClass>("return ConstClassFunction()");
  EXPECT_EQ(expected_result, actual_result);
}

//----------------------------------------------------------------------------------------------------------------------
// Test0ParametersReturnInvalid
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindFunctionTest, Test0ParametersReturnInvalid)
{
  ::testing::StrictMock<MockFunctionWithNoParameters> mock;
  MockFunctionWithNoParameters::m_instance = &mock;

  InvalidStackUtilsClass expected_result;
  EXPECT_CALL(mock, ClassInvalidFunction())
    .Times(2)
    .WillRepeatedly(::testing::Return(expected_result));

  // bind the objects required for calling the function
  //
  sq_pushroottable(m_vm);

  EXPECT_TRUE(sqb::Bind::BindFunction(m_vm, -1, &MockFunctionWithNoParameters::InvalidFunction, _SC("InvalidFunction")));

  sq_pushstring(m_vm, _SC("Mock"), -1);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_TRUE(sqb::Bind::BindFunction(m_vm, 3, &MockFunctionWithNoParameters::InvalidFunction, _SC("InvalidFunction")));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -3));

  sq_poptop(m_vm);

  CompileAndFailCall("InvalidFunction()");
  CheckErrorString("error returning object of type 'InvalidStackUtilsClass'");
  CompileAndFailCall("Mock.InvalidFunction()");
  CheckErrorString("error returning object of type 'InvalidStackUtilsClass'");
}

//----------------------------------------------------------------------------------------------------------------------
// Test0ParametersReturnInvalid
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindClassFunctionTest, Test0ParametersReturnInvalid)
{
  ::testing::StrictMock<MockFunctionWithNoParameters> mock;
  MockFunctionWithNoParameters::m_instance = &mock;

  InvalidStackUtilsClass expected_result;
  EXPECT_CALL(mock, ClassInvalidFunction())
    .WillOnce(::testing::Return(expected_result));
  EXPECT_CALL(mock, ConstClassInvalidFunction())
    .WillOnce(::testing::Return(expected_result));

  // bind the objects required for calling the function
  //
  sq_pushroottable(m_vm);

  // bind the mock class
  //
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_settypetag(m_vm, -1, sqb::ClassTypeTag<MockFunctionWithNoParameters>::Get()));
  HSQOBJECT class_object;
  sq_getstackobj(m_vm, -1, &class_object);
  sqb::ClassTypeTag<MockFunctionWithNoParameters>::Get()->SetClassObject(m_vm, class_object);
  EXPECT_TRUE(sqb::Bind::BindClassFunction<MockFunctionWithNoParameters>(m_vm, -1, &MockFunctionWithNoParameters::ClassInvalidFunction, _SC("ClassInvalidFunction")));
  EXPECT_TRUE(sqb::Bind::BindClassFunction<MockFunctionWithNoParameters>(m_vm, 2, &MockFunctionWithNoParameters::ConstClassInvalidFunction, _SC("ConstClassInvalidFunction")));
  sq_poptop(m_vm);

  EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, static_cast<MockFunctionWithNoParameters *>(&mock), _SC("mock")));

  sq_poptop(m_vm);

  CompileAndFailCall("mock.ClassInvalidFunction()");
  CheckErrorString("error returning object of type 'InvalidStackUtilsClass'");
  CompileAndFailCall("mock.ConstClassInvalidFunction()");
  CheckErrorString("error returning object of type 'InvalidStackUtilsClass'");
}

//----------------------------------------------------------------------------------------------------------------------
// Test0ParametersReturnInvalid
//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindSingletonFunctionTest, Test0ParametersReturnInvalid)
{
  ::testing::StrictMock<MockFunctionWithNoParameters> mock;
  MockFunctionWithNoParameters::m_instance = &mock;

  InvalidStackUtilsClass expected_result;
  EXPECT_CALL(mock, ClassInvalidFunction())
    .WillOnce(::testing::Return(expected_result));
  EXPECT_CALL(mock, ConstClassInvalidFunction())
    .WillOnce(::testing::Return(expected_result));

  // bind the objects required for calling the function
  //
  sq_pushroottable(m_vm);

  // bind the mock class
  //
  sq_pushstring(m_vm, _SC("Mock"), -1);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_TRUE(sqb::Bind::BindSingletonFunction<MockFunctionWithNoParameters>(m_vm, -1, &mock, &MockFunctionWithNoParameters::ClassInvalidFunction, _SC("ClassInvalidFunction")));
  EXPECT_TRUE(sqb::Bind::BindSingletonFunction<MockFunctionWithNoParameters>(m_vm, 1, &mock, &MockFunctionWithNoParameters::ConstClassInvalidFunction, _SC("ConstClassInvalidFunction")));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -3));

  sq_poptop(m_vm);

  CompileAndFailCall("Mock.ClassInvalidFunction()");
  CheckErrorString("error returning object of type 'InvalidStackUtilsClass'");
  CompileAndFailCall("ConstClassInvalidFunction()");
  CheckErrorString("error returning object of type 'InvalidStackUtilsClass'");
}
